package org.hepeng.workx.spring.boot.error;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hepeng.workx.service.RestServiceCallResult;
import org.hepeng.workx.service.error.ServerError;
import org.hepeng.workx.web.util.HttpResponseUtils;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.core.env.Environment;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author he peng
 */
public class ErrorFilter extends OncePerRequestFilter {

    private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper();

    private RouteLocator routeLocator;

    private Environment env;

    private ErrorAttributes errorAttributes;

    private ObjectMapper objectMapper;

    private ServerProperties serverProperties;

    public ErrorFilter(RouteLocator routeLocator, Environment env, ErrorAttributes errorAttributes, ObjectMapper objectMapper, ServerProperties serverProperties) {
        this.routeLocator = routeLocator;
        this.env = env;
        this.errorAttributes = errorAttributes;
        this.objectMapper = objectMapper;
        this.serverProperties = serverProperties;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request , response);
        } catch (Throwable t) {
            String path = serverProperties.getError().getPath();
            String requestUri = URL_PATH_HELPER.getRequestUri(request);
            if (StringUtils.equals(path , requestUri)) {
                error(t , request , response);
            }
        }
    }

    private void error(Throwable error , HttpServletRequest request, HttpServletResponse response) throws IOException {
        ErrorBody errorBody = ErrorBody.newErrorBody(new ServletWebRequest(request) , this.errorAttributes , this.env , this.routeLocator);
        List<Map<String, String>> oldErrors = errorBody.getErrors();

        List<Map<String , String>> errors = new ArrayList<>();
        Map<String , String> errorMap = new HashMap<>();
        errorMap.put("error" , error.getClass().getName());
        errorMap.put("errorMsg" , ExceptionUtils.getMessage(error));
        Throwable rootError = ExceptionUtils.getRootCause(error);
        String rootException = Objects.nonNull(rootError) ? rootError.getClass().getName() : "";
        String rootExceptionMsg = Objects.nonNull(rootError) ? ExceptionUtils.getMessage(rootError) : "";
        errorMap.put("rootError" , rootException);
        errorMap.put("rootErrorMsg" , rootExceptionMsg);
        errors.add(errorMap);
        errors.addAll(oldErrors);

        errorBody.setErrors(errors);

        RestServiceCallResult callResult = RestServiceCallResult.restBuilder()
                .error(ServerError.SERVER_ERROR)
                .data(errorBody)
                .build();
        HttpResponseUtils.writeAndFlushJson(response , this.objectMapper.writeValueAsString(callResult));
    }
}
